package mosdi.util;

import java.util.Arrays;
import java.util.StringTokenizer;

import mosdi.fa.Alphabet;
import mosdi.fa.GeneralizedString;

public class Iupac {

	private static final Alphabet dnaAlphabet = new Alphabet(Arrays.asList('A', 'C', 'G', 'T'));
	private static final Alphabet iupacAlphabet = new Alphabet(Arrays.asList('A', 'C', 'G', 'T', 'R', 'Y', 'S', 'W', 'K', 'M', 'B', 'D', 'H', 'V', 'N'));
	private static int[] multiplicityTable = null;
		
	public static final Alphabet getDnaAlphabet() {
		return dnaAlphabet;
	}
	
	public static final Alphabet getAlphabet() {
		return iupacAlphabet;
	}
	
	public static BitArray iupacCharToBitArray(Alphabet alphabet, char c) {
		BitArray ba = new BitArray(alphabet.size());
		// IUPAC Alphabet, see http://www.bioinformatics.org/sms2/iupac.html
		// A.................Adenine
		// C.................Cytosine
		// G.................Guanine
		// T (or U)..........Thymine (or Uracil)
		// R.................A or G
		// Y.................C or T
		// S.................G or C
		// W.................A or T
		// K.................G or T
		// M.................A or C
		// B.................C or G or T
		// D.................A or G or T
		// H.................A or C or T
		// V.................A or C or G
		// N.................any base
		if ((c=='A')||(c=='R')||(c=='W')||(c=='M')||(c=='D')||(c=='H')||(c=='V')||(c=='N')) {
			ba.set(alphabet.getIndex('A'), true);
		}
		if ((c=='C')||(c=='Y')||(c=='S')||(c=='M')||(c=='B')||(c=='H')||(c=='V')||(c=='N')) {
			ba.set(alphabet.getIndex('C'), true);
		}
		if ((c=='G')||(c=='R')||(c=='S')||(c=='K')||(c=='B')||(c=='D')||(c=='V')||(c=='N')) {
			ba.set(alphabet.getIndex('G'), true);
		}
		if ((c=='T')||(c=='Y')||(c=='W')||(c=='K')||(c=='B')||(c=='D')||(c=='H')||(c=='N')) {
			ba.set(alphabet.getIndex('T'), true);
		}
		if (ba.allZero()) {
			throw new IllegalArgumentException("Invalid IUPAC-String");
		}
		return ba;
	}
	
	public static GeneralizedString iupacToGeneralizedString(Alphabet alphabet, String iupacString) {
			BitArray[] positions = new BitArray[iupacString.length()];
			for (int i=0; i<iupacString.length(); ++i) {
				positions[i]=iupacCharToBitArray(alphabet, iupacString.charAt(i));
			}
			GeneralizedString gs = new GeneralizedString(alphabet, positions);
	//		Log.println(Log.Level.DEBUG, gs.toString());
			return gs;
		}

	/** Transforms a IUPAC String into its reverse complementary. */
	public static String reverseComplementary(String forward) {
		StringBuilder sb = new StringBuilder(forward.length());
		for (int i=forward.length()-1; i>=0; --i) {
			switch (forward.charAt(i)) {
			case 'A': sb.append('T'); break;
			case 'C': sb.append('G'); break;
			case 'G': sb.append('C'); break;
			case 'T': sb.append('A'); break;
			case 'R': sb.append('Y'); break;
			case 'Y': sb.append('R'); break;
			case 'S': sb.append('S'); break;
			case 'W': sb.append('W'); break;
			case 'K': sb.append('M'); break;
			case 'M': sb.append('K'); break;
			case 'B': sb.append('V'); break;
			case 'D': sb.append('H'); break;
			case 'H': sb.append('D'); break;
			case 'V': sb.append('B'); break;
			case 'N': sb.append('N'); break;
			}
		}
		return sb.toString();
	}
	
	public static int[] parseAbelianPattern(String s) {
		StringTokenizer stChars = new StringTokenizer(s,"0123456789",false);
		StringTokenizer stFreq = new StringTokenizer(s,"ACGTRYSWKMBDHVN",false);
		Alphabet iupacAlphabet = getAlphabet();
		int[] result = new int[iupacAlphabet.size()];
		while (stChars.hasMoreTokens()) {
			String token = stChars.nextToken();
			if (token.length()!=1) return null;
			if (!stFreq.hasMoreTokens()) return null;
			int freq = Integer.parseInt(stFreq.nextToken());
			result[iupacAlphabet.getIndex(token.charAt(0))]=freq;
		}
		return result;
	}
	
	private static void buildMultiplicityTable() {
		multiplicityTable = new int[iupacAlphabet.size()];
		String s = "ACGTRYSWKMBDHVN";
		GeneralizedString gs = iupacToGeneralizedString(dnaAlphabet,s);
		for (int i=0; i<iupacAlphabet.size(); ++i) {
			multiplicityTable[iupacAlphabet.getIndex(s.charAt(i))] = gs.getPosition(i).numberOfOnes();
		}
	}

	public static int getCharacterMultiplicity(int charIndex) {
		if (multiplicityTable==null) buildMultiplicityTable();
		return multiplicityTable[charIndex];
	}
	
	public static int getCharacterMultiplicity(char c) {
		try {
			return getCharacterMultiplicity(iupacAlphabet.getIndex(c));
		} catch (ArrayIndexOutOfBoundsException e) {
			throw new IllegalArgumentException("Invalid IUPAC character: \""+c+"\"");
		}
	}
	
}
